/*
 * MIT License
 *
 * Copyright (c) 2023-present, tangli
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package tony.test.crypto.asymmetric

import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import org.junit.jupiter.api.Assertions.assertArrayEquals
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.Assertions.assertTrue
import tony.crypto.asymmetric.AsymmetricCrypto
import tony.crypto.asymmetric.Sm2
import java.security.KeyPair

/**
 * AsymmetricCrypto 接口测试类
 *
 * 测试非对称加密接口的核心功能，包括密钥生成、加密解密、数字签名等
 *
 * @author tangli
 * @date 2025/01/22 20:00
 */
@DisplayName("AsymmetricCrypto 接口测试")
class AsymmetricCryptoTest {

    /**
     * 测试用的非对称加密实现
     */
    private val crypto: AsymmetricCrypto = Sm2

    @Nested
    @DisplayName("密钥生成测试")
    inner class KeyGenerationTest {

        @Test
        @DisplayName("generateKeyPair(): 应该成功生成密钥对")
        fun testGenerateKeyPair() {
            // When
            val keyPair: KeyPair = crypto.generateKeyPair()

            // Then
            assertNotNull(keyPair, "密钥对不应为空")
            assertNotNull(keyPair.public, "公钥不应为空")
            assertNotNull(keyPair.private, "私钥不应为空")
            assertNotNull(keyPair.public.encoded, "公钥编码不应为空")
            assertNotNull(keyPair.private.encoded, "私钥编码不应为空")
        }

        @Test
        @DisplayName("generateKeyPair(): 多次生成应该产生不同的密钥对")
        fun testGenerateMultipleKeyPairs() {
            // When
            val keyPair1: KeyPair = crypto.generateKeyPair()
            val keyPair2: KeyPair = crypto.generateKeyPair()

            // Then
            assertFalse(
                keyPair1.public.encoded.contentEquals(keyPair2.public.encoded),
                "不同次生成的公钥应该不同"
            )
            assertFalse(
                keyPair1.private.encoded.contentEquals(keyPair2.private.encoded),
                "不同次生成的私钥应该不同"
            )
        }
    }

    @Nested
    @DisplayName("加密解密测试")
    inner class EncryptDecryptTest {

        private val keyPair: KeyPair = crypto.generateKeyPair()
        private val publicKey: ByteArray = keyPair.public.encoded
        private val privateKey: ByteArray = keyPair.private.encoded

        @Test
        @DisplayName("encrypt() 和 decrypt(): 应该正确加密和解密数据")
        fun testEncryptAndDecrypt() {
            // Given
            val plaintext = "Hello, World! 你好世界！".toByteArray(Charsets.UTF_8)

            // When
            val ciphertext: ByteArray = crypto.encrypt(plaintext, publicKey)
            val decrypted: ByteArray = crypto.decrypt(ciphertext, privateKey)

            // Then
            assertNotNull(ciphertext, "密文不应为空")
            assertTrue(ciphertext.isNotEmpty(), "密文不应为空数组")
            assertArrayEquals(plaintext, decrypted, "解密后的数据应该与原始数据相同")
        }

        @Test
        @DisplayName("encrypt(): 空数据应该抛出异常")
        fun testEncryptEmptyData() {
            // Given
            val emptyData = ByteArray(0)

            // When & Then
            assertThrows<IllegalArgumentException> {
                crypto.encrypt(emptyData, publicKey)
            }
        }

        @Test
        @DisplayName("decrypt(): 空数据应该抛出异常")
        fun testDecryptEmptyData() {
            // Given
            val emptyData = ByteArray(0)

            // When & Then
            assertThrows<IllegalArgumentException> {
                crypto.decrypt(emptyData, privateKey)
            }
        }

        @Test
        @DisplayName("encrypt(): 大数据块应该正确处理")
        fun testEncryptLargeData() {
            // Given
            val largeData = "A".repeat(1000).toByteArray(Charsets.UTF_8)

            // When
            val ciphertext: ByteArray = crypto.encrypt(largeData, publicKey)
            val decrypted: ByteArray = crypto.decrypt(ciphertext, privateKey)

            // Then
            assertArrayEquals(largeData, decrypted, "大数据块解密后应该与原始数据相同")
        }

        @Test
        @DisplayName("decrypt(): 无效密文应该抛出异常")
        fun testDecryptInvalidCiphertext() {
            // Given
            val invalidCiphertext = "invalid_ciphertext".toByteArray()

            // When & Then
            assertThrows<Exception> {
                crypto.decrypt(invalidCiphertext, privateKey)
            }
        }
    }

    @Nested
    @DisplayName("数字签名测试")
    inner class SignatureTest {

        private val keyPair: KeyPair = crypto.generateKeyPair()
        private val publicKey: ByteArray = keyPair.public.encoded
        private val privateKey: ByteArray = keyPair.private.encoded

        @Test
        @DisplayName("sign() 和 verify(): 应该正确签名和验证")
        fun testSignAndVerify() {
            // Given
            val data = "Test data for signature".toByteArray(Charsets.UTF_8)

            // When
            val signature: ByteArray = crypto.sign(data, privateKey)
            val isValid: Boolean = crypto.verify(data, signature, publicKey)

            // Then
            assertNotNull(signature, "签名不应为空")
            assertTrue(signature.isNotEmpty(), "签名不应为空数组")
            assertTrue(isValid, "签名验证应该成功")
        }

        @Test
        @DisplayName("verify(): 错误的签名应该验证失败")
        fun testVerifyInvalidSignature() {
            // Given
            val data = "Test data".toByteArray(Charsets.UTF_8)
            val signature = crypto.sign(data, privateKey)
            val tamperedData = "Tampered data".toByteArray(Charsets.UTF_8)

            // When
            val isValid: Boolean = crypto.verify(tamperedData, signature, publicKey)

            // Then
            assertFalse(isValid, "被篡改数据的签名验证应该失败")
        }

        @Test
        @DisplayName("verify(): 错误的公钥应该验证失败")
        fun testVerifyWithWrongPublicKey() {
            // Given
            val data = "Test data".toByteArray(Charsets.UTF_8)
            val signature = crypto.sign(data, privateKey)
            val wrongKeyPair = crypto.generateKeyPair()
            val wrongPublicKey = wrongKeyPair.public.encoded

            // When
            val isValid: Boolean = crypto.verify(data, signature, wrongPublicKey)

            // Then
            assertFalse(isValid, "使用错误公钥的签名验证应该失败")
        }

        @Test
        @DisplayName("sign(): 空数据应该返回空签名")
        fun testSignEmptyData() {
            // Given
            val emptyData = ByteArray(0)

            // When
            val signature: ByteArray = crypto.sign(emptyData, privateKey)

            // Then
            assertTrue(signature.isEmpty(), "空数据的签名应该为空")
        }

        @Test
        @DisplayName("verify(): 空数据和空签名应该验证成功")
        fun testVerifyEmptyDataAndSignature() {
            // Given
            val emptyData = ByteArray(0)
            val emptySignature = ByteArray(0)

            // When
            val isValid: Boolean = crypto.verify(emptyData, emptySignature, publicKey)

            // Then
            assertTrue(isValid, "空数据和空签名应该验证成功")
        }

        @Test
        @DisplayName("verify(): 只有数据为空应该验证失败")
        fun testVerifyOnlyDataEmpty() {
            // Given
            val emptyData = ByteArray(0)
            val nonEmptySignature = "signature".toByteArray()

            // When
            val isValid: Boolean = crypto.verify(emptyData, nonEmptySignature, publicKey)

            // Then
            assertFalse(isValid, "只有数据为空时验证应该失败")
        }

        @Test
        @DisplayName("verify(): 只有签名为空应该验证失败")
        fun testVerifyOnlySignatureEmpty() {
            // Given
            val nonEmptyData = "data".toByteArray()
            val emptySignature = ByteArray(0)

            // When
            val isValid: Boolean = crypto.verify(nonEmptyData, emptySignature, publicKey)

            // Then
            assertFalse(isValid, "只有签名为空时验证应该失败")
        }
    }

    @Nested
    @DisplayName("跨密钥对测试")
    inner class CrossKeyPairTest {

        @Test
        @DisplayName("encrypt() 和 decrypt(): 不同密钥对应该无法解密")
        fun testEncryptDecryptWithDifferentKeyPairs() {
            // Given
            val keyPair1 = crypto.generateKeyPair()
            val keyPair2 = crypto.generateKeyPair()
            val plaintext = "Test data".toByteArray(Charsets.UTF_8)

            // When
            val ciphertext = crypto.encrypt(plaintext, keyPair1.public.encoded)

            // Then
            assertThrows<Exception> {
                crypto.decrypt(ciphertext, keyPair2.private.encoded)
            }
        }
    }
}
